import {Vector3} from './Vector3.js'

/**
 * Primary reference:
 *   https://graphics.stanford.edu/papers/envmap/envmap.pdf
 *
 * Secondary reference:
 *   https://www.ppsloan.org/publications/StupidSH36.pdf
 */

// 3-band SH defined by 9 coefficients

class SphericalHarmonics3 {
  constructor() {
    Object.defineProperty(this, 'isSphericalHarmonics3', {value: true})

    this.coefficients = []

    for (let i = 0; i < 9; i++) {
      this.coefficients.push(new Vector3())
    }
  }

  set(coefficients) {
    for (let i = 0; i < 9; i++) {
      this.coefficients[i].copy(coefficients[i])
    }

    return this
  }

  zero() {
    for (let i = 0; i < 9; i++) {
      this.coefficients[i].set(0, 0, 0)
    }

    return this
  }

  // get the radiance in the direction of the normal
  // target is a Vector3
  getAt(normal, target) {
    // normal is assumed to be unit length

    const x = normal.x,
      y = normal.y,
      z = normal.z

    const coeff = this.coefficients

    // band 0
    target.copy(coeff[0]).multiplyScalar(0.282095)

    // band 1
    target.addScaledVector(coeff[1], 0.488603 * y)
    target.addScaledVector(coeff[2], 0.488603 * z)
    target.addScaledVector(coeff[3], 0.488603 * x)

    // band 2
    target.addScaledVector(coeff[4], 1.092548 * (x * y))
    target.addScaledVector(coeff[5], 1.092548 * (y * z))
    target.addScaledVector(coeff[6], 0.315392 * (3.0 * z * z - 1.0))
    target.addScaledVector(coeff[7], 1.092548 * (x * z))
    target.addScaledVector(coeff[8], 0.546274 * (x * x - y * y))

    return target
  }

  // get the irradiance (radiance convolved with cosine lobe) in the direction of the normal
  // target is a Vector3
  // https://graphics.stanford.edu/papers/envmap/envmap.pdf
  getIrradianceAt(normal, target) {
    // normal is assumed to be unit length

    const x = normal.x,
      y = normal.y,
      z = normal.z

    const coeff = this.coefficients

    // band 0
    target.copy(coeff[0]).multiplyScalar(0.886227) // π * 0.282095

    // band 1
    target.addScaledVector(coeff[1], 2.0 * 0.511664 * y) // ( 2 * π / 3 ) * 0.488603
    target.addScaledVector(coeff[2], 2.0 * 0.511664 * z)
    target.addScaledVector(coeff[3], 2.0 * 0.511664 * x)

    // band 2
    target.addScaledVector(coeff[4], 2.0 * 0.429043 * x * y) // ( π / 4 ) * 1.092548
    target.addScaledVector(coeff[5], 2.0 * 0.429043 * y * z)
    target.addScaledVector(coeff[6], 0.743125 * z * z - 0.247708) // ( π / 4 ) * 0.315392 * 3
    target.addScaledVector(coeff[7], 2.0 * 0.429043 * x * z)
    target.addScaledVector(coeff[8], 0.429043 * (x * x - y * y)) // ( π / 4 ) * 0.546274

    return target
  }

  add(sh) {
    for (let i = 0; i < 9; i++) {
      this.coefficients[i].add(sh.coefficients[i])
    }

    return this
  }

  addScaledSH(sh, s) {
    for (let i = 0; i < 9; i++) {
      this.coefficients[i].addScaledVector(sh.coefficients[i], s)
    }

    return this
  }

  scale(s) {
    for (let i = 0; i < 9; i++) {
      this.coefficients[i].multiplyScalar(s)
    }

    return this
  }

  lerp(sh, alpha) {
    for (let i = 0; i < 9; i++) {
      this.coefficients[i].lerp(sh.coefficients[i], alpha)
    }

    return this
  }

  equals(sh) {
    for (let i = 0; i < 9; i++) {
      if (!this.coefficients[i].equals(sh.coefficients[i])) {
        return false
      }
    }

    return true
  }

  copy(sh) {
    return this.set(sh.coefficients)
  }

  clone() {
    return new this.constructor().copy(this)
  }

  fromArray(array, offset) {
    if (offset === undefined) offset = 0

    const coefficients = this.coefficients

    for (let i = 0; i < 9; i++) {
      coefficients[i].fromArray(array, offset + i * 3)
    }

    return this
  }

  toArray(array, offset) {
    if (array === undefined) array = []
    if (offset === undefined) offset = 0

    const coefficients = this.coefficients

    for (let i = 0; i < 9; i++) {
      coefficients[i].toArray(array, offset + i * 3)
    }

    return array
  }

  // evaluate the basis functions
  // shBasis is an Array[ 9 ]
  static getBasisAt(normal, shBasis) {
    // normal is assumed to be unit length

    const x = normal.x,
      y = normal.y,
      z = normal.z

    // band 0
    shBasis[0] = 0.282095

    // band 1
    shBasis[1] = 0.488603 * y
    shBasis[2] = 0.488603 * z
    shBasis[3] = 0.488603 * x

    // band 2
    shBasis[4] = 1.092548 * x * y
    shBasis[5] = 1.092548 * y * z
    shBasis[6] = 0.315392 * (3 * z * z - 1)
    shBasis[7] = 1.092548 * x * z
    shBasis[8] = 0.546274 * (x * x - y * y)
  }
}

export {SphericalHarmonics3}
